#include "ParseNode.h"
#include "GenerateICode.h"
#include "Symbol.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

UInt8 icodeData[INTERMEDIATE_CODE_DATA_SIZE] = {0};
struct Instruction* icode;

UInt8 variableData[VARIABLE_DATA_SIZE] = {0};
struct Variable* variables;

struct Variable* HasVariable(struct ParseNode* pn)
{
	if (pn->type == PNT_IDENTIFIER)
		return PSUB_SYMBOL(PSUB_PARSENODE(pn, struct IdentifierParseNode)->identifier,
            struct VariableSymbol)->variable;
	else if (pn->type == PNT_TEMP_VAR)
		return PSUB_PARSENODE(pn, struct TemporaryVariableParseNode)->variable;

	return NULL;
}

static const char* parseNodeTypeStrings[] =
{
	"PNT_ASSIGN",
	"PNT_ADD_ASSIGN",
	"PNT_SUB_ASSIGN",

	"PNT_MUL_ASSIGN",
	"PNT_DIV_ASSIGN",

	"PNT_DIV",
	"PNT_MUL",
	"PNT_ADD",
	"PNT_SUB",

	"PNT_GOTO",

	"PNT_PRE_INCREMENT",
	"PNT_POST_INCREMENT",
	"PNT_PRE_DECREMENT",
	"PNT_POST_DECREMENT",

	"PNT_IDENTIFIER",
	"PNT_TEMP_VAR",
	"PNT_CONST"
};

#define HANDLE_ASSIGN_PARSE_NODE(VAR_TO_VAR_INS, VAR_TO_VAR_INS_TYPE, CONSTANT_TO_VAR_INS, CONSTANT_TO_VAR_INS_TYPE)\
	{\
		struct Variable* pn0v = HasVariable(pn0);\
		struct Variable* pn1v = HasVariable(pn1);\
		\
		rpn = pn0;\
		\
		if (pn1v)\
		{\
			VAR_TO_VAR_INS* i;\
			\
			i = ADD_INSTRUCTION(VAR_TO_VAR_INS, VAR_TO_VAR_INS_TYPE);\
			i->dest = pn0v;\
			i->src = pn1v;\
		}\
		else\
		{\
			CONSTANT_TO_VAR_INS* i;\
			\
			i = ADD_INSTRUCTION(CONSTANT_TO_VAR_INS, CONSTANT_TO_VAR_INS_TYPE);\
			i->dest = pn0v;\
			i->src = PSUB_PARSENODE(pn1, struct ConstParseNode)->value;\
		}\
	}

// We have a div/mul/add/sub expression (not assign)
// Ex: (3 + 4)
// 1. Create temporary variable
// 2. Create assign instruction (temp variable = first sub parse node arg*)
// 3. Create sub instruction (temp variable -= second sub parse node arg*)
// *Can be constant/variable- proper Instruction must be determined
#define HANDLE_TWO_ARG_PARSE_NODE(VAR_TO_VAR_INS, VAR_TO_VAR_INS_TYPE, CONSTANT_TO_VAR_INS, CONSTANT_TO_VAR_INS_TYPE)\
	{\
		struct Variable* rpnv;\
		struct Variable* pn0v;\
		struct Variable* pn1v;\
		\
		PN_ALLOC(rpn, struct TemporaryVariableParseNode);\
		PN_SET_TYPE(rpn, PNT_TEMP_VAR);\
		PSUB_PARSENODE(rpn, struct TemporaryVariableParseNode)->variable = ADD_TEMPVARIABLE(struct Int32Variable, VT_INT32);\
		\
		rpnv = PSUB_PARSENODE(rpn, struct TemporaryVariableParseNode)->variable;\
		\
		pn0v = HasVariable(pn0);\
		pn1v = HasVariable(pn1);\
		\
		if (pn0v && pn1v)\
		{\
			struct AssignVarToVarIns* i0;\
			VAR_TO_VAR_INS* i1;\
			\
			i0 = ADD_INSTRUCTION(struct AssignVarToVarIns, IT_ASSIGN_VAR_TO_VAR);\
			i0->dest = rpnv;\
			i0->src = pn0v;\
			\
			i1 = ADD_INSTRUCTION(VAR_TO_VAR_INS, VAR_TO_VAR_INS_TYPE);\
			i1->dest = rpnv;\
			i1->src = pn1v;\
		}\
		else if (pn0->type == PNT_CONST && pn1->type == PNT_CONST)\
		{\
			struct AssignConstantToVarIns* i0;\
			CONSTANT_TO_VAR_INS* i1;\
			\
			i0 = ADD_INSTRUCTION(struct AssignConstantToVarIns, IT_ASSIGN_CONSTANT_TO_VAR);\
			i0->dest = rpnv;\
			i0->src = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg0, struct ConstParseNode)->value;\
			i0->src.type = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg0, struct ConstParseNode)->value.type;\
			\
			i1 = ADD_INSTRUCTION(CONSTANT_TO_VAR_INS, CONSTANT_TO_VAR_INS_TYPE);\
			i1->dest = rpnv;\
			i1->src  = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value;\
			i1->src.type = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value.type;\
		}\
		else if (pn0v && pn1->type == PNT_CONST)\
		{\
			struct AssignVarToVarIns* i0;\
			CONSTANT_TO_VAR_INS* i1;\
			\
			i0 = ADD_INSTRUCTION(struct AssignVarToVarIns, IT_ASSIGN_VAR_TO_VAR);\
			i0->dest = rpnv;\
			i0->src = pn0v;\
			\
			i1 = ADD_INSTRUCTION(CONSTANT_TO_VAR_INS, CONSTANT_TO_VAR_INS_TYPE);\
			i1->dest = rpnv;\
			i1->src = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value;\
			i1->src.type = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value.type;\
		}\
		else if (pn0->type == PNT_CONST && pn1v)\
		{\
			struct AssignConstantToVarIns* i0;\
			VAR_TO_VAR_INS* i1;\
			\
			i0 = ADD_INSTRUCTION(struct AssignConstantToVarIns, IT_ASSIGN_CONSTANT_TO_VAR);\
			i0->dest = rpnv;\
			i0->src = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg0, struct ConstParseNode)->value;\
			i0->src.type = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg0, struct ConstParseNode)->value.type;\
			\
			i1 = ADD_INSTRUCTION(VAR_TO_VAR_INS, VAR_TO_VAR_INS_TYPE);\
			i1->dest = rpnv;\
			i1->src = pn1v;\
		}\
	}

static struct ParseNode* GenerateICodeR(struct ParseNode* pn)
{
	// Variable from arg0
	struct ParseNode* pn0 = NULL;

	// Variable from arg1 (if exists)
	struct ParseNode* pn1 = NULL;

	// Returned variable
	struct ParseNode* rpn = NULL;

	switch (pn->type)
	{
	case PNT_ASSIGN:
	case PNT_ADD_ASSIGN:
	case PNT_SUB_ASSIGN:
	case PNT_MUL_ASSIGN:
	case PNT_DIV_ASSIGN:
	case PNT_DIV:
	case PNT_MUL:
	case PNT_ADD:
	case PNT_SUB:
		pn0 = GenerateICodeR(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg0);
		pn1 = GenerateICodeR(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1);
		break;

	case PNT_GOTO:
	case PNT_PRE_INCREMENT:
	case PNT_POST_INCREMENT:
	case PNT_PRE_DECREMENT:
	case PNT_POST_DECREMENT:
		pn0 = GenerateICodeR(PSUB_PARSENODE(pn, struct OneArgParseNode)->arg0);
		break;

    case PNT_TEMP_VAR:
	case PNT_IDENTIFIER:
	case PNT_CONST:
		break;
	}

	/* we need some way of having the ADD_ASSIGN node accessing the output of an
	ADD node => temporary variable, if that is the rval.
	*/
	// preorder(node)
	// print node.value
	// if node.left then preorder(node.left)
	// if node.right null then preorder(node.right)

	printf("%s\n", parseNodeTypeStrings[pn->type]);

	switch (pn->type)
	{
	case PNT_ASSIGN:
		{
			struct Variable* rpnv;

			rpn = pn0;
			rpnv = PSUB_SYMBOL(PSUB_PARSENODE(rpn, struct IdentifierParseNode)->identifier, struct VariableSymbol)->variable;

			if (rpn)
			{
				struct Variable* pn1v;
rvValid:
				if (!rpnv)
				{
					// Make a new variable, it's not a temp
					struct VariableSymbol* vs = PSUB_SYMBOL(PSUB_PARSENODE(rpn, struct IdentifierParseNode)->identifier, struct VariableSymbol);

					struct Symbol* symbol = PSUB_SYMBOL(rpn, struct IdentifierParseNode)->identifier;
					rpnv = vs->variable = ADD_VARIABLE(struct Int32Variable, VT_INT32, PSUB_SYMBOL(rpn, struct IdentifierParseNode)->identifier);
				}

				if ((pn1v = HasVariable(pn1)))
				{
					struct AssignVarToVarIns* i0;

					i0 = ADD_INSTRUCTION(struct AssignVarToVarIns, IT_ASSIGN_VAR_TO_VAR);
					i0->dest = rpnv;
					i0->src = pn1v;
				}
				else
				{
					struct AssignConstantToVarIns* i0;

					i0 = ADD_INSTRUCTION(struct AssignConstantToVarIns, IT_ASSIGN_CONSTANT_TO_VAR);
					i0->dest = rpnv;
					i0->src = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value;
					i0->src.type = PSUB_PARSENODE(PSUB_PARSENODE(pn, struct TwoArgParseNode)->arg1, struct ConstParseNode)->value.type;
				}
			}
			else
			{
				PN_ALLOC(rpn, struct TemporaryVariableParseNode);
				PN_SET_TYPE(rpn, PNT_TEMP_VAR);
				PSUB_PARSENODE(rpn, struct TemporaryVariableParseNode)->variable = ADD_TEMPVARIABLE(struct Int32Variable, VT_INT32);
				rpnv = PSUB_PARSENODE(rpn, struct TemporaryVariableParseNode)->variable;

				goto rvValid;
			}
			break;
		}
	case PNT_ADD_ASSIGN:
		HANDLE_ASSIGN_PARSE_NODE(struct AddVarToVarIns, IT_ADD_VAR_TO_VAR, struct AddConstantToVarIns, IT_ADD_CONSTANT_TO_VAR);
		break;

	case PNT_SUB_ASSIGN:
		HANDLE_ASSIGN_PARSE_NODE(struct SubVarFromVarIns, IT_SUB_VAR_FROM_VAR, struct SubConstantFromVarIns, IT_SUB_CONSTANT_FROM_VAR);
		break;

	case PNT_MUL_ASSIGN:
		HANDLE_ASSIGN_PARSE_NODE(struct MulVarByVarIns, IT_MUL_VAR_BY_VAR, struct MulVarByConstantIns, IT_MUL_VAR_BY_CONSTANT);
		break;

	case PNT_DIV_ASSIGN:
		HANDLE_ASSIGN_PARSE_NODE(struct DivVarByVarIns, IT_DIV_VAR_BY_VAR, struct DivVarByConstantIns, IT_DIV_VAR_BY_CONSTANT);
		break;

	case PNT_DIV:
		HANDLE_TWO_ARG_PARSE_NODE(struct DivVarByVarIns, IT_DIV_VAR_BY_VAR, struct DivVarByConstantIns, IT_DIV_VAR_BY_CONSTANT);
		break;

	case PNT_MUL:
		HANDLE_TWO_ARG_PARSE_NODE(struct MulVarByVarIns, IT_MUL_VAR_BY_VAR, struct MulVarByConstantIns, IT_MUL_VAR_BY_CONSTANT);
		break;

	case PNT_ADD:
		HANDLE_TWO_ARG_PARSE_NODE(struct AddVarToVarIns, IT_ADD_VAR_TO_VAR, struct AddConstantToVarIns, IT_ADD_CONSTANT_TO_VAR);
		break;

	case PNT_SUB:
		HANDLE_TWO_ARG_PARSE_NODE(struct SubVarFromVarIns, IT_SUB_VAR_FROM_VAR, struct SubConstantFromVarIns, IT_SUB_CONSTANT_FROM_VAR);
		break;

	case PNT_GOTO:
		break;
	case PNT_PRE_INCREMENT:
	case PNT_POST_INCREMENT:
	case PNT_PRE_DECREMENT:
	case PNT_POST_DECREMENT:
		return PSUB_PARSENODE(pn, struct OneArgParseNode)->arg0;
		break;

	case PNT_IDENTIFIER:
	case PNT_CONST:
	case PNT_TEMP_VAR:
		rpn = pn;
		break;
	}

	return rpn;
}

void GenerateICode()
{
	{
		struct ParseNode** pns;
		for (pns = statements + 1; *pns; ++pns)
		{
			GenerateICodeR(*pns);
			printf("EOS\n");
		}
	}
	{
		struct ReturnIns* retIns = ADD_INSTRUCTION(struct ReturnIns, IT_RETURN);
		retIns->returnCode = 0x0;
	}
}
